package org.dromara.commission.listener.redisPlus;

import lombok.SneakyThrows;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.redis.utils.RedisUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author 小将lcz
 * @version 1.0
 * @description 将 1-m 的大数据表 上传到redis并构建redis索引表的工具
 * @date 2024/7/28 8:34
 */

public class RedisPlusUtil {

    /***
     * @description  获取索引表， 通过redisPlus的导入监听类RedisPlusImportListener的子类来获取
     * @param clazz
     * @return RedisIndexTable
     */
    public static <T extends RedisPlusImportListener> RedisIndexTable initIndexTable(Class<T> clazz){
        String headStr =  GlobalConstants.GLOBAL_REDIS_KEY + clazz.getName()+":";
        if(RedisUtils.isExistsObject(headStr + "indexTable")){
            //redis存在,用redis的
            return RedisUtils.getCacheObject(headStr + "indexTable");
        }
        else{
            //根据类名来创建一个
            return new RedisIndexTable(headStr);
        }
    }

    /***
    * @description push更新到redis服务器
    * @param listHashMap
     * @param indexTable
    * @return void
    */
    @SneakyThrows
    public static  <T extends IRedisPlusEntity> StringBuilder push(HashMap<String,List<T>> listHashMap, RedisIndexTable indexTable) {
        int successNum = 0;
        int failureNum = 0;
        int addNum = 0;//添加的记录数
        int updateNum = 0;//更新的记录数
        StringBuilder successMsg = new StringBuilder();
        StringBuilder failureMsg = new StringBuilder();
        //如果redis中存在有索引表就用redis里面的
        if(RedisUtils.isExistsObject(indexTable.getIndexTableKey())){
            indexTable = RedisUtils.getCacheObject(indexTable.getIndexTableKey());
        }
        for (Map.Entry<String, List<T>> entry : listHashMap.entrySet()) {
            String key = entry.getKey();
            List<T> value = entry.getValue();//获取一个样例值用来做flag比对

            //判断是要新月份覆盖还是同月份追加
            RedisIndexTable.Item oldItem = indexTable.getItems().get(indexTable.getItemEndKey(key));
            T newItem = value.get(0);
            Object flag =  newItem.getFlag();// 调用 getFlag() 方法
            int slice = 0;//初始的片数
            if(oldItem != null){
                //存在该商家的记录
                //判断是同月份还是新月份(调用 judge() 方法)
                RedisPlusJudgeType judgeType = newItem.judge(oldItem.getFlag());
                if(judgeType.equals(RedisPlusJudgeType.JUDGE_TYPE_ADD)){
                    //同月份，追加
                    //将原来的 _end 片更新成数字片
                    //更新redis,后面新的_end插入之后redisIndexTable会自动put覆盖（这也是将RedisIndexTable.items设置成HashMap的妙处之一）
                    List<Object> cacheList = RedisUtils.getCacheList(oldItem.getKey());
                    //移除redis中的(不知道为啥一定要移除。set不能覆盖，而是追加)
                    RedisUtils.deleteObject(oldItem.getKey());
                    oldItem = indexTable.toSliceItem(oldItem);//转换成数字片
                    RedisUtils.setCacheList(oldItem.getKey(),cacheList);
                    slice = oldItem.getSlice()+1;//片数记录器+1
                    addNum += value.size();//追加添加的记录数
                } else if (judgeType.equals(RedisPlusJudgeType.JUDGE_TYPE_UPDATE)){
                    //新月份，覆盖
                    //删除所有keyHead前缀的redis缓存
                    RedisUtils.deleteKeys(indexTable.getItemPreKey(key));
                    //删除索引表中keyHead的item
                    indexTable.deleteItemByKeyHead(key);
                    updateNum += value.size();//覆盖更新的记录数
                } else if (judgeType.equals(RedisPlusJudgeType.JUDGE_TYPE_THROW)) {
                    //丢弃
                    continue;
                } else if (judgeType.equals(RedisPlusJudgeType.JUDGE_TYPE_NONE)) {
                    //啥也不是，我继续干我的活
                    addNum += value.size();//都是新增加
                }
            }else{
                addNum += value.size();//都是新增加
            }

            //如果list太大就进行拆分，避免单个key太大
            if (value.size() > indexTable.ITEM_SIZE) {
                // 拆分列表并上传到 Redis
                for (int i = 0; i < value.size(); i = i + indexTable.ITEM_SIZE) {
                    int endIndex = Math.min(i + indexTable.ITEM_SIZE, value.size());
                    String newKey;
                    //如果是最后一片
                    if(endIndex == value.size()){
                        int size = endIndex - i;
                        newKey = indexTable.addEndItem(new RedisIndexTable.Item(key, slice, size, flag));//加入尾片的item
                    }else{
                        newKey = indexTable.addItem(new RedisIndexTable.Item(key, slice, indexTable.ITEM_SIZE, flag));//加入新的item
                    }
                    RedisUtils.setCacheList(newKey, value.subList(i, endIndex));
                    slice++;//片数+1
                }
            }
            else {
                String newKey = indexTable.addEndItem(new RedisIndexTable.Item(key, slice, value.size(), flag));//加入新的item
                // 直接上传到 Redis
                RedisUtils.setCacheList(newKey, value);
            }
            //刷新redis中的indexTable
            RedisUtils.setCacheObject(indexTable.getIndexTableKey(), indexTable);
            successNum += value.size();//成功记录数
        }
        //返回分析结果
        if (failureNum > 0) {
            failureMsg.insert(0, "很抱歉，导入失败！共 " + failureNum + " 条数据格式不正确，错误如下：");
            throw new ServiceException(failureMsg.toString());
        } else {
            successMsg.insert(0, "恭喜您，数据已全部导入成功！共 " + successNum + " 条：");
            successMsg.append("<br/>").append(addNum).append(" 条新增成功");
            successMsg.append("<br/>").append(updateNum).append(" 条更新成功");
        }
        return successMsg;
    }

    /***
    * @description 清空原来的redis数据和索引表
    * @param indexTable
    * @return
    */
    public static void clear(RedisIndexTable indexTable) {
        //清空所有前缀头的redis缓存数据
        RedisUtils.deleteKeys(indexTable.getHeadStr()+"*");
    }

    /***
     * @description 获取redis数据
     * @param key
     * @return
     */
    public static <T extends IRedisPlusEntity> List<T> getCacheList(String key) {
        //清空所有前缀头的redis缓存数据
        List<T> cacheList = RedisUtils.getCacheList(key);
        return cacheList;
    }

    /***
     * @description 根据查询窗口获取redis数据
     * @param window
     * @return
     */
    public static <T> List<T> getCacheListByWindow(RedisIndexTable.Window window) {
        List<T> list = new ArrayList<>();
        for (RedisIndexTable.Item item : window.getItems()) {
            List<T> cacheList = RedisUtils.getCacheList(item.getKey());
            list.addAll(cacheList);
        }
        int endIndex = Math.min(window.getEndIndex()+1, list.size());
        return list.subList(window.getBeginOffset(), endIndex);
    }


}
